<?php
declare(strict_types=1);

namespace App\Helper;

/**
 * @property array $data
 * @property array $children
 * @property array $family
 */
class TreeHelper
{
    private $strId; // ID的Key名称
    private $strFid; // 父ID的Key名称
    private $strName;
    private $arrRoot = []; // 根节点ID，可能多个
    private $arrData = []; // 以id为key的数据
    private $arrSons = []; // 以id为key的 父亲 -> 儿子
    private $arrChildren = []; // 以id为key的 祖先 -> 后代
    private $arrFamily = []; // 以多维数组表示的族谱

    /**
     * 构造
     * @param array $arrSrc
     * @param string $strId
     * @param string $strFid
     * @param string $strName
     */
    public function __construct(
        array $arrSrc,
        string $strId = 'id',
        string $strFid = 'parent_id',
        string $strName = 'name'
    ) {
        $this->strId = $strId;
        $this->strFid = $strFid;
        $this->strName = $strName;
        $this->prepare($arrSrc);
    }

    /**
     * 数据预处理
     * @param array $arrSrc
     * @return void
     */
    private function prepare(array $arrSrc): void
    {
        foreach ($arrSrc as $arr) {
            $strId = strval($arr[$this->strId]);
            $this->arrData[$strId] = $arr;
            $strFid = strval(intval($arr[$this->strFid] ?? 0) ?: '');
            if (strlen($strFid)) {
                isset($this->arrSons[$strFid]) || $this->arrSons[$strFid] = [];
                array_push($this->arrSons[$strFid], $strId);
            } else {
                array_push($this->arrRoot, $strId);
            }
        }
        foreach ($this->arrRoot as $strId) {
            array_push($this->arrFamily, $this->getFamily($strId));
            $this->getChildren($strId);
        }
    }

    /**
     * 取族谱
     * @param string $strId
     * @return array|null
     */
    private function getFamily(string $strId): ?array
    {
        $arrRe = $this->getData($strId);
        $arrSons = $this->getSons($strId);
        foreach ($arrSons as $strSonId) {
            isset($arrRe['children']) || $arrRe['children'] = [];
            array_push($arrRe['children'], $this->getFamily($strSonId));
        }
        return $arrRe;
    }

    /**
     * 按id取数据
     * @param string or int $strId
     * @return array|null
     */
    private function getData($strId): ?array
    {
        return $this->arrData[strval($strId)] ?? null;
    }

    /**
     * 取节点所有的儿子
     * @param string or int $strId
     * @return array
     */
    private function getSons($strId): array
    {
        return $this->arrSons[strval($strId)] ?? [];
    }

    /**
     * 取后代ID集合
     * @param $strId
     * @return array
     */
    private function getChildren($strId): array
    {
        $arrIds = [];
        $arrSonIds = $this->getSons($strId);
        foreach ($arrSonIds as $sonId) {
            array_push($arrIds, $sonId);
            $arrIds = array_merge($arrIds, $this->getChildren($sonId));
        }
        $this->arrChildren[$strId] = $arrIds;
        return $arrIds;
    }

    /**
     * getter
     * @param string $str
     * @return array|null
     */
    public function __get(string $str): ?array
    {
        $str = ucfirst($str);
        $strK = 'arr' . $str;
        if (isset($this->$strK)) {
            return $this->$strK;
        }
        $strK = 'str' . $str;
        return $this->$strK ?? null;
    }

    /**
     * 获取下拉框所有选项
     * @param array|null $arr
     * @param array $lastInfo
     * @return array
     */
    public function getSelectOptions(?array $arr = null, array $lastInfo = []): array
    {
        is_null($arr) && $arr = $this->arrFamily;
        $data = [];
        $len = count($arr);
        foreach ($arr as $i => $item) {
            $myLastInfo = $lastInfo;
            array_push($myLastInfo, ($i + 1 == $len));
            array_push($data, [
                $this->strId => $item[$this->strId],
                $this->strName => static::getOptionLabel($item[$this->strName], $myLastInfo),
                'raw_' . $this->strName => $item[$this->strName],
            ]);
            $data = array_merge($data, $this->getSelectOptions($item['children'] ?? [], $myLastInfo));
        }
        return $data;
    }

    /**
     * 下拉框选项中显示的内容
     * @param string $name
     * @param array $lastInfo
     * @return string
     */
    private static function getOptionLabel(string $name, array $lastInfo): string
    {
        $str = '';
        $count = count($lastInfo);
        foreach ($lastInfo as $i => $last) {
            if ($i + 1 == $count) {
                $str .= ($last ? '└─ ' : '├─ ') . $name;
            } else {
                $str .= ($last ? '　　' : '│　');
            }
        }
        return $str;
    }
}

/*
$arr = [
    ['id' => 1, 'info' => 'I am 1'],
    ['id' => 2, 'fid' => 1, 'info' => 'I am 2'],
    ['id' => 3, 'fid' => 1, 'info' => 'I am 3'],
    ['id' => 4, 'fid' => 2, 'info' => 'I am 4'],
    ['id' => 5, 'fid' => 2, 'info' => 'I am 5'],
    ['id' => 6, 'fid' => 3, 'info' => 'I am 6'],
    ['id' => 7, 'fid' => 3, 'info' => 'I am 7'],
    ['id' => 8, 'fid' => 4, 'info' => 'I am 8'],
];
$objTree = new TreeHelper($arr, 'id', 'fid', 'info');
$arr = $objTree->getSelectOptions();
foreach ($arr as $a) {
    echo $a['info'] . PHP_EOL;
}
*/
